/**
 * Copyright (C) 2009+ Spark Zheng
 *
 * @file        src/dserver_tools.c 
 * @date        2010-05-23
 * @brief	server_tools
 * @see		dserver         
 * @history
 */

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <dlib/global.h>
#include <dlib/str.h>

#include "utils.h"

static char strres[128] = {0};

static union val {
	int		i_val;
	long		l_val;
	struct linger	linger_val;
	struct timeval	timeval_val;
} val;

static int check_sock_options(void *arg);
static int check_rcvbuf(void *arg);

static char *sock_str_flag(union val *, int);
static char *sock_str_int(union val *, int);
static char *sock_str_linger(union val *, int);
static char *sock_str_timeval(union val *, int);

static struct sock_opts {
	const char	*opt_str;
	int		opt_level;
	int		opt_name;
	char *(*opt_val_str)(union val *, int);
} sock_opts[] = {
	{"SO_BROADCAST", SOL_SOCKET, SO_BROADCAST, sock_str_flag},
	{"SO_DEBUG", SOL_SOCKET, SO_DEBUG, sock_str_flag},
	{"SO_DONTROUTE", SOL_SOCKET, SO_DONTROUTE, sock_str_flag},
	{"SO_ERROR", SOL_SOCKET, SO_ERROR, sock_str_int},
	{"SO_KEEPALIVE", SOL_SOCKET, SO_KEEPALIVE, sock_str_flag},
	{"SO_LINGER", SOL_SOCKET, SO_LINGER, sock_str_linger},
	{"SO_OOBINLINE", SOL_SOCKET, SO_OOBINLINE, sock_str_flag},
	{"SO_RCVBUF", SOL_SOCKET, SO_RCVBUF, sock_str_int},
	{"SO_SNDBUF", SOL_SOCKET, SO_SNDBUF, sock_str_int},
	{"SO_RCVLOWAT", SOL_SOCKET, SO_RCVLOWAT, sock_str_int},
	{"SO_SNDLOWAT", SOL_SOCKET, SO_SNDLOWAT, sock_str_int},
	{"SO_RCVTIMEO", SOL_SOCKET, SO_RCVTIMEO, sock_str_timeval},
	{"SO_SNDTIMEO", SOL_SOCKET, SO_SNDTIMEO, sock_str_timeval},
	{"SO_REUSEADDR", SOL_SOCKET, SO_REUSEADDR, sock_str_flag},
#ifdef SO_REUSEPORT
	{"SO_REUSEPORT", SOL_SOCKET, SO_REUSEPORT, sock_str_flag},
#else
	{"SO_REUSEPORT", 0, 0, NULL},
#endif
	{"SO_TYPE", SOL_SOCKET, SO_TYPE, sock_str_int},
#ifdef SO_USELOOPBACK
	{"SO_USELOOPBACK", SOL_SOCKET, SO_USELOOPBACK, sock_str_flag},
#else
	{"SO_USELOOPBACK", 0, 0, NULL},
#endif
	{"IP_TOS", IPPROTO_IP, IP_TOS, sock_str_int},
	{"IP_TTL", IPPROTO_IP, IP_TTL, sock_str_int},
#ifdef IPV6_DONTFRAG
	{"IPV6_DONTFRAG", IPPROTO_IPV6, IPV6_DONTFRAG, sock_str_flag},
#else
	{"IPV6_DONTFRAG", 0, 0, NULL},
#endif
#ifdef IPV6_UNICAST_HOPS
	{"IPV6_UNICAST_HOPS", IPPROTO_IPV6, IPV6_UNICAST_HOPS, sock_str_int},
#else
	{"IPV6_UNICAST_HOPS", 0, 0, NULL},
#endif
#ifdef IPV6_V6ONLY
	{"IPV6_V6ONLY", IPPROTO_IPV6, IPV6_V6ONLY, sock_str_flag},
#else
	{"IPV6_V6ONLY", 0, 0, NULL},
#endif
	{"TCP_MAXSEG", IPPROTO_TCP, TCP_MAXSEG, sock_str_int},
	{"TCP_NODELAY", IPPROTO_TCP, TCP_NODELAY, sock_str_flag},
#ifdef SCTP_AUTOCLOSE
	{"SCTP_AUTOCLOSE", IPPROTO_SCTP, SCTP_AUTOCLOSE, sock_str_int},
#else
	{"SCTP_AUTOCLOSE", 0, 0, NULL},
#endif
#ifdef SCTP_MAXBURST
	{"SCTP_MAXBURST", IPPROTO_SCTP, SCTP_MAXBURST, sock_str_int},
#else
	{"SCTP_MAXBURST", 0, 0, NULL},
#endif
#ifdef SCTP_MAXSEG
	{"SCTP_MAXSEG", IPPROTO_SCTP, SCTP_MAXSEG, sock_str_int},
#else
	{"SCTP_MAXSEG", 0, 0, NULL},
#endif
#ifdef SCTP_NODELAY
	{"SCTP_NODELAY", IPPROTO_SCTP, SCTP_NODELAY, sock_str_flag},
#else
	{"SCTP_NODELAY", 0, 0, NULL},
#endif
	{NULL, 0, 0, NULL}
};

static struct dserver_commands {
	const char	*cmd_name;
	int (*cmd_callback)(void *arg);
} cmds[] = {
	{"check_sock_options", check_sock_options},
	{"check_rcvbuf", check_rcvbuf},
	{NULL, NULL}
};

int main(int argc, char **argv)
{
	char *cmd_name;
	void *arg;
	struct dserver_commands *ptr;

	if (argc == 1) {
		cmd_name = "check_sock_options";
		arg = NULL;
	} else {
		cmd_name = argv[1];
		if (argc > 1) {
			arg = (void *)argv[2];
		} else {
			arg = NULL;
		}
	}

	for (ptr = cmds; ptr->cmd_name != NULL; ptr++) {
		if (str_compare(ptr->cmd_name, cmd_name) == 0) {
			(void)(*ptr->cmd_callback)(arg);
			break;
		}
	}

	return 0;
}

static int check_sock_options(void *arg)
{
	int fd;
	socklen_t len;
	struct sock_opts *ptr;

	UNUSED_PARAM(arg);

	printf("\nIN check_sock_options:\n");

	for (ptr = sock_opts; ptr->opt_str != NULL; ptr++) {
		printf("%s: ", ptr->opt_str);
		if (ptr->opt_val_str == NULL) {
			printf("(undefined)\n");
		} else {
			switch (ptr->opt_level) {
			case SOL_SOCKET:
			case IPPROTO_IP:
			case IPPROTO_TCP:
				fd = socket(AF_INET, SOCK_STREAM, 0);
				break;
#ifdef IPV6
			case IPPROTO_IPV6:
				fd = socket(AF_INET6, SOCK_STREAM, 0);
				break;
#endif
#ifdef IPPROTO_SCTP
			case IPPROTO_SCTP:
				fd = socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP);
				break;
#endif
			default:
				printf("Can't create fd for level %d\n", ptr->opt_level);
				continue;
			}

			len = sizeof (val);
			if (getsockopt(fd, ptr->opt_level, ptr->opt_name, &val, &len) == -1) {
				printf("getsockopt error\n");
			} else {
				printf("default = %s\n", (*ptr->opt_val_str)(&val, len));
			}
			close(fd);
		}
	}

	printf("check_sock_options END.\n");

	return 0;
}

static int check_rcvbuf(void *arg)
{
	int sockfd, rcvbuf, mss;
	socklen_t len;
	struct sockaddr_in servaddr;

	if (arg == NULL)
		return -1;

	const char *ip_addr = (const char *)arg;

	sockfd = socket(AF_INET, SOCK_STREAM, 0);

	printf("\nIN check_rcvbuf:\n");

	len = sizeof(rcvbuf);
	getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, &len);
	len = sizeof(mss);
	getsockopt(sockfd, IPPROTO_TCP, TCP_MAXSEG, &mss, &len);

	printf("defaults: SO_RCVBUF = %d, MSS = %d\n", rcvbuf, mss);

	rcvbuf = 9973; /* a prime number */
	setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf));
	len = sizeof(rcvbuf);
	getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, &len);

	printf("SO_RCVBUF = %d (after setting it to 9973)\n", rcvbuf);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(22); /* daytime server */
	inet_pton(AF_INET, ip_addr, &servaddr.sin_addr);

	connect(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr));

	len = sizeof(rcvbuf);
	getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, &len);
	len = sizeof(mss);
	getsockopt(sockfd, IPPROTO_TCP, TCP_MAXSEG, &mss, &len);

	printf("after connect: SO_RCVBUF = %d, MSS = %d\n", rcvbuf, mss);

	printf("check_rcvbuf END.\n");

	return 0;
}

static char *sock_str_flag(union val *ptr, int len)
{
	if (len != sizeof(int))
		snprintf(strres, sizeof(strres), "size(%d) not sizeof(int)", len);
	else
		snprintf(strres, sizeof(strres), "%s", (ptr->i_val == 0) ? "off" : "on");
	return strres;
}

static char *sock_str_int(union val *ptr, int len)
{
	if (len != sizeof (int))
		snprintf(strres, sizeof(strres), "size(%d) not sizeof(int)", len);
	else
		snprintf(strres, sizeof(strres), "%d", ptr->i_val);
	return strres;
}

static char *sock_str_linger(union val *ptr, int len)
{
	struct linger *lptr = &ptr->linger_val;

	if (len != sizeof (struct linger))
		snprintf(strres, sizeof(strres), "size(%d) not sizeof(struct linger)", len);
	else
		snprintf(strres, sizeof(strres), "l_onoff = %d, l_linger = %d",
				lptr->l_onoff, lptr->l_linger);
	return strres;
}

static char *sock_str_timeval(union val *ptr, int len)
{
	struct timeval *tvptr = &ptr->timeval_val;

	if (len != sizeof (struct timeval))
		snprintf(strres, sizeof(strres), "size(%d) not sizeof(struct timeval)", len);
	else
		snprintf(strres, sizeof(strres), "%lu sec, %lu usec", tvptr->tv_sec, tvptr->tv_usec);
	return strres;
}

